using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Logging;

namespace DocumentCreationSystem.Services
{
    public interface ICodeIntelligenceService
    {
        Task InitializeAsync(string projectPath, CancellationToken ct = default);
        Task<IReadOnlyList<Diagnostic>> GetDiagnosticsAsync(string? projectPath = null, CancellationToken ct = default);
        Task<(ISymbol? symbol, Document? document)> GetSymbolAtAsync(string filePath, int line, int column, string? projectPath = null, CancellationToken ct = default);
        Task<IReadOnlyList<(string file, int line, int column, string lineText)>> FindReferencesAsync(string filePath, int line, int column, string? projectPath = null, CancellationToken ct = default);
        Task<string> RenameSymbolPreviewAsync(string filePath, int line, int column, string newName, string? projectPath = null, CancellationToken ct = default);
    }

    public class CodeIntelligenceService : ICodeIntelligenceService
    {
        private readonly ILogger<CodeIntelligenceService> _logger;
        private static bool _msbuildRegistered;
        private readonly ConcurrentDictionary<string, (MSBuildWorkspace ws, Project proj)> _cache = new();

        public CodeIntelligenceService(ILogger<CodeIntelligenceService> logger)
        {
            _logger = logger;
        }

        public async Task InitializeAsync(string projectPath, CancellationToken ct = default)
        {
            projectPath = NormalizeProjectPath(projectPath);
            EnsureMSBuild();
            if (_cache.ContainsKey(projectPath)) return;

            var ws = MSBuildWorkspace.Create(new Dictionary<string, string>
            {
                // 减少还原风险，避免联网
                ["SkipUnrecognizedProjects"] = "true"
            });
            ws.WorkspaceFailed += (_, e) => _logger.LogWarning($"MSBuildWorkspace 警告: {e.Diagnostic}");

            _logger.LogInformation($"打开项目: {projectPath}");
            var proj = await ws.OpenProjectAsync(projectPath, progress: null, cancellationToken: ct);
            _cache[projectPath] = (ws, proj);
        }

        public async Task<IReadOnlyList<Diagnostic>> GetDiagnosticsAsync(string? projectPath = null, CancellationToken ct = default)
        {
            var (ws, proj) = await GetWorkspaceProjectAsync(projectPath, ct);
            var compilation = await proj.GetCompilationAsync(ct);
            var diags = compilation?.GetDiagnostics(ct) ?? ImmutableArray<Diagnostic>.Empty;
            return diags.Where(d => d.Severity >= DiagnosticSeverity.Warning)
                .Take(500)
                .ToList();
        }

        public async Task<(ISymbol? symbol, Document? document)> GetSymbolAtAsync(string filePath, int line, int column, string? projectPath = null, CancellationToken ct = default)
        {
            var (ws, proj) = await GetWorkspaceProjectAsync(projectPath, ct);
            var doc = proj.Documents.FirstOrDefault(d => PathsEqual(d.FilePath, filePath));
            if (doc == null) return (null, null);

            var source = await doc.GetTextAsync(ct);
            var pos = GetPositionFromLineColumn(source, line, column);
            var model = await doc.GetSemanticModelAsync(ct);
            if (model == null) return (null, doc);

            var symbol = await SymbolFinder.FindSymbolAtPositionAsync(model, pos, ws, ct);
            return (symbol, doc);
        }

        public async Task<IReadOnlyList<(string file, int line, int column, string lineText)>> FindReferencesAsync(string filePath, int line, int column, string? projectPath = null, CancellationToken ct = default)
        {
            var (symbol, doc) = await GetSymbolAtAsync(filePath, line, column, projectPath, ct);
            if (symbol == null || doc == null) return Array.Empty<(string, int, int, string)>();

            var solution = doc.Project.Solution;
            var refs = await SymbolFinder.FindReferencesAsync(symbol, solution, cancellationToken: ct);
            var results = new List<(string file, int line, int column, string lineText)>();

            foreach (var r in refs)
            {
                foreach (var loc in r.Locations)
                {
                    var location = loc.Location;
                    if (!location.IsInSource) continue;
                    var span = location.SourceSpan;
                    var refDoc = solution.GetDocument(location.SourceTree);
                    if (refDoc == null) continue;
                    var text = await refDoc.GetTextAsync(ct);
                    var lsp = text.Lines.GetLineFromPosition(span.Start);
                    results.Add((refDoc.FilePath ?? "", lsp.LineNumber + 1, span.Start - lsp.Start + 1, lsp.ToString().Trim()));
                }
            }
            return results.Take(500).ToList();
        }

        public async Task<string> RenameSymbolPreviewAsync(string filePath, int line, int column, string newName, string? projectPath = null, CancellationToken ct = default)
        {
            var (symbol, doc) = await GetSymbolAtAsync(filePath, line, column, projectPath, ct);
            if (symbol == null || doc == null) return "❌ 未能定位到符号";

            var solution = doc.Project.Solution;
            var options = new SymbolRenameOptions(true, false, false);
            var renamed = await Renamer.RenameSymbolAsync(solution, symbol, options, newName, ct);

            // 生成统一 diff 预览
            var sb = new StringBuilder();
            var changes = renamed.GetChanges(solution);
            foreach (var projChange in changes.GetProjectChanges())
            {
                foreach (var docId in projChange.GetChangedDocuments())
                {
                    var oldDoc = solution.GetDocument(docId)!;
                    var newDoc = renamed.GetDocument(docId)!;
                    var oldText = await oldDoc.GetTextAsync(ct);
                    var newText = await newDoc.GetTextAsync(ct);
                    var diff = CreateUnifiedDiff(oldDoc.FilePath ?? "", oldText.ToString(), newText.ToString());
                    if (!string.IsNullOrEmpty(diff)) sb.AppendLine(diff);
                }
            }
            if (sb.Length == 0) return "(无变更)";
            return sb.ToString();
        }

        private void EnsureMSBuild()
        {
            if (_msbuildRegistered) return;
            try
            {
                MSBuildLocator.RegisterDefaults();
                _msbuildRegistered = true;
                _logger.LogInformation("MSBuildLocator 已注册");
            }
            catch (InvalidOperationException)
            {
                _msbuildRegistered = true; // 已注册
            }
        }

        private async Task<(MSBuildWorkspace ws, Project proj)> GetWorkspaceProjectAsync(string? projectPath, CancellationToken ct)
        {
            var resolved = NormalizeProjectPath(projectPath);
            if (_cache.TryGetValue(resolved, out var tuple)) return tuple;
            await InitializeAsync(resolved, ct);
            return _cache[resolved];
        }

        private static string NormalizeProjectPath(string? projectPath)
        {
            if (!string.IsNullOrWhiteSpace(projectPath)) return Path.GetFullPath(projectPath);
            // 默认使用当前应用的主项目
            var defaultProj = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..","..","..","DocumentCreationSystem.csproj"));
            return defaultProj;
        }

        private static int GetPositionFromLineColumn(SourceText text, int line, int column)
        {
            var l = Math.Max(0, Math.Min(text.Lines.Count - 1, line - 1));
            var c = Math.Max(0, column - 1);
            return text.Lines[l].Start + c;
        }

        private static bool PathsEqual(string? a, string? b)
        {
            if (a == null || b == null) return false;
            return string.Equals(Path.GetFullPath(a), Path.GetFullPath(b), StringComparison.OrdinalIgnoreCase);
        }

        // 生成简单的统一 diff（逐行比较）
        private static string CreateUnifiedDiff(string filePath, string oldText, string newText)
        {
            var oldLines = oldText.Replace("\r\n", "\n").Split('\n');
            var newLines = newText.Replace("\r\n", "\n").Split('\n');
            var sb = new StringBuilder();
            if (oldText == newText) return string.Empty;
            sb.AppendLine($"--- a/{filePath}");
            sb.AppendLine($"+++ b/{filePath}");

            int i = 0, j = 0;
            while (i < oldLines.Length || j < newLines.Length)
            {
                if (i < oldLines.Length && j < newLines.Length && oldLines[i] == newLines[j])
                {
                    i++; j++;
                    continue;
                }
                var startI = i; var startJ = j;
                var hunk = new List<string>();
                while (i < oldLines.Length && (j >= newLines.Length || oldLines[i] != newLines[j]))
                {
                    hunk.Add($"-{oldLines[i]}"); i++;
                }
                while (j < newLines.Length && (i >= oldLines.Length || oldLines[i] != newLines[j]))
                {
                    hunk.Add($"+{newLines[j]}"); j++;
                }
                sb.AppendLine($"@@ -{startI + 1},{Math.Max(0, i - startI)} +{startJ + 1},{Math.Max(0, j - startJ)} @@");
                foreach (var l in hunk) sb.AppendLine(l);
            }
            return sb.ToString();
        }
    }
}

